/**
 *
 * 212所报告
 *
 * Created by yangyunze on 2024/06/04
 */

const fs = require('fs-extra');
const path = require('path');
const compressing = require('compressing');
const moment = require('moment');
const { Types } = require('mongoose');

const Docx = require('./../adapter/docx');
const DefaultReporter = require('./../default-report');
const ProjectVersionSpliter = require('../../project-version-spliter');
const fileElements = require('./../../../file-elements');
const tests = require('./../../../tests');
const utility = require('./../../../utility');
const { type: taskType } = require('./../../../tasks');
const { kind: fileKind } = require('./../../../files');
const Task = require('./../../../mongo/task');
const File = require('./../../../mongo/file');

class Reporter extends DefaultReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format, complete) {
    super(versionId, locale, version, project, creator, outputDir, format, complete);
    this.baseDir = __dirname;
    this.reportTime = moment().format('YYYY年MM月');
    this.funcTestResultStatistic = {};
    this.enabledCoverageKind = [];
  }

  async generate(progressIndicator) {
    const fileName = '软件单元测试报告';
    const result = {
      cachePath: this.outputDir,
    };
    const toolInfo = require('../../../../../package.json');
    const toolVersion = toolInfo.version;
    ['statement', 'branch', 'condition', 'decision', 'mcdc'].forEach((key) => {
      if (this.version.coverages & tests.coverageKind[key]) {
        const o = {
          key,
          coverageKey: `${key}Coverage`,
          totalKey: `${key}Total`,
          coveredKey: `${key}Covered`,
          name: this.i18n.__(`tests.${key}Coverage`),
        };
        this.enabledCoverageKind.push(o);
      }
    });
    const { versionPath, projectDir } = await this.spliteByProjectVersion();
    this.versionDir = versionPath;
    this.projectDir = projectDir;
    const reportPath = `${path.join(this.versionDir, fileName)}`;
    const [files, tasks] = await Promise.all([
      this.spliteByFile(progressIndicator),
      Task.find({ versionId: this.version._id }, ['type']),
    ]);
    const [functionsOverview] = await this.getDataAdapter().getFunctionsOverview(this.versionId, { pageSize: 999999 });
    functionsOverview.forEach((overview, index) => {
      overview.index = index + 1;
      overview.coverageInfo = [];
      this.enabledCoverageKind.forEach((coverageKind) => {
        let coverage = '-';
        if (overview[coverageKind.totalKey]) {
          coverage = overview[coverageKind.coveredKey]
            / overview[coverageKind.totalKey];
          coverage = `${Math.round(coverage * 10000) / 100}%`;
        }
        const o = {
          coverage,
        };
        overview.coverageInfo.push(o);
      });
      overview.passedCount = 0;
      overview.failedCount = overview.testCount;
      if (this.funcTestResultStatistic[overview._id.toString()]) {
        overview.passedCount = this.funcTestResultStatistic[overview._id.toString()].passedCount;
        overview.failedCount = overview.testCount - overview.passedCount;
      }
    });
    await this.generateFileReport(
      {
        projectName: this.project.projectName,
        versionName: this.version.versionName,
        toolVersion,
        enabledCoverageKind: this.enabledCoverageKind,
        language: this.version.language,
        funcsOverview: functionsOverview,
        files: files.filter((file) => file.functions.length > 0),
        reportTime: this.reportTime,
        reportDate: moment().format('YYYY年MM月DD日'),
        tasks: tasks.map((task) => {
          let name = '';
          switch (task.type) {
            case taskType.parse:
              name = '源码解析';
              break;
            case taskType.testCaseGenerate:
              name = '生成用例';
              break;
            case taskType.testCaseRun:
              name = '执行用例';
              break;
            case taskType.autoTest:
              name = '一键测试';
              break;
            case taskType.autoRegression:
              name = '一键回归';
              break;
            case taskType.autoTestAndFilter:
              name = '一键补充测试';
              break;
            default:
              return null;
          }
          return {
            name,
            user: this.creator.username,
            date: moment(Types.ObjectId(task._id).getTimestamp()).format('YYYY-MM-DD HH:mm:ss'),
          };
        }).filter((task) => task),
      },
      reportPath
    );
    result.reportPath = path.join(this.outputDir, `${fileName}.zip`);
    const reportsDir = path.join(this.outputDir, this.project.projectName);
    await compressing.zip.compressDir(reportsDir, result.reportPath);
    return result;
  }

  async spliteByFile(progressIndicator) {
    await fs.ensureDir(this.versionDir);
    const sourceFileExts = [];
    const languages = { ...this.version.languages, headers: this.version.headers };
    Object.keys(languages).forEach((key) => {
      if (languages[key].suffixes) {
        sourceFileExts.push(...languages[key].suffixes);
      }
    });

    const resFiles = new Map();
    const filter = {
      versionId: this.versionId,
      $or: [
        { kind: fileKind.file },
        { kind: fileKind.fileOutsideProject },
      ],
      $and: [{ fileName: { $nin: ['.gitignore', '.smartrocket'] } }],
    };
    if (sourceFileExts && sourceFileExts.length > 0) {
      filter.$or.push({
        exname: { $in: sourceFileExts },
      });
    }
    const allFiles = await File.find(filter, ['_id', 'fileName', 'path']).lean();
    allFiles.forEach((file) => {
      resFiles.set(file._id.toString(), file);
    });
    return this.handleFiles([...resFiles.values()], this.versionDir, progressIndicator);
  }

  async handleFiles(files, versionDir, progressIndicator) {
    return utility.arrayChunkOperation(files, 1, async (file) => {
      const result = await this.handleFile(file, versionDir);
      if (progressIndicator) {
        progressIndicator.advance(1 / files.length);
      }
      return result;
    });
  }

  async handleFile(file, versionDir) {
    const res = await ProjectVersionSpliter.prototype.handleFile.call(this, file, versionDir);
    const { typeSystem, functions } = res;
    const fileSummary = DefaultReporter.getSummaryOfFile(functions);
    const errorTests = [];
    const failedTests = [];
    let errorTestIdx = 1;
    let failedTestsIdx = 1;
    const result = {
      fileName: file.fileName,
      filePath: file.path,
      summary: fileSummary,
      functions: functions.filter((func) => fileElements.isTestableElement(func.kind) && func.tests.length > 0).map((func) => {
        const testInfoMap = {};
        func.tests.forEach((test) => {
          testInfoMap[test._id.toString()] = {
            log: test.log,
          };
        });
        const result = {};
        result.enabledCoverageKindCount = this.enabledCoverageKind.length;
        result.summary = this.getSummaryOfFunction(func);
        result.tests = this.getTestDetailsOfFunction(typeSystem, func);
        result.functionName = func.functionName;
        result.index = func.index;
        let passedCount = 0;
        result.tests.forEach((test, index) => {
          test.index = index + 1;
          const newInputDetails = [];
          const newOutputDetails = [];
          if (test.resultKind === 'succeed' || test.resultKind === 'nonStrictSucceed' || test.resultKind === 'noResult') {
            passedCount += 1;
          } else if (test.resultKind === 'codeDefect' || test.resultKind === 'exception') {
            errorTests.push({
              // eslint-disable-next-line no-plusplus
              idx: errorTestIdx++,
              desc: testInfoMap[test.testId.toString()].log.location
                ? `错误位置：${testInfoMap[test.testId.toString()].log.location.file} ${testInfoMap[test.testId.toString()].log.location.line}行${testInfoMap[test.testId.toString()].log.location.column}列`
                : '',
              kind: (test.resultKind === 'codeDefect') ? 1 : 2,
              funcName: func.functionName,
              testId: test.identifier,
            });
          }
          if (utility.isArrayType(test.detail.input)) {
            let isParam = false;
            let inputCount = 0;
            for (let i = 0, len = test.detail.input.length; i < len; i += 1) {
              const input = test.detail.input[i];
              // 处理变量值显示格式
              if (input.value !== '') {
                input.value = ` = ${input.value}`;
              }
              if (input.category) {
                isParam = ['形参', '全局变量'].includes(input.category);
              }
              if (input.variable) {
                inputCount += 1;
              }
              if (input.category && input.variable) {
                newInputDetails.push({
                  category: `${input.category}:`,
                  value: '',
                  variable: '',
                  isParam,
                });
                newInputDetails.push({
                  category: '',
                  value: input.value,
                  variable: input.variable,
                  isParam,
                });
              } else {
                input.isParam = isParam;
                newInputDetails.push(input);
              }
            }
            test.detail.inputCount = inputCount;
          }
          if (utility.isArrayType(test.detail.output)) {
            test.detail.output.forEach((output) => {
              // eslint-disable-next-line no-control-regex
              output.variable = output.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
              if (test.resultKind === 'failed' && output.value !== '' && output.value !== output.result) {
                failedTests.push({
                  // eslint-disable-next-line no-plusplus
                  idx: failedTestsIdx++,
                  desc: `${output.variable}, 实际值=${output.result}, 期望值=${output.value}`,
                  kind: 0,
                  funcName: func.functionName,
                  testId: test.identifier,
                });
              }
              // eslint-disable-next-line no-nested-ternary
              output.value = output.value !== ''
                ? ` = ${output.value}`
                : (output.result !== '' ? ` = ${output.result}` : '');
              // 处理变量值显示格式
              if (output.result !== '') {
                output.result = ` = ${output.result}`;
              }
              if (output.category && output.variable) {
                newOutputDetails.push({
                  category: `${output.category}:`,
                  value: '',
                  result: '',
                  variable: '',
                });
                newOutputDetails.push({
                  category: '',
                  value: output.value,
                  result: output.result,
                  variable: output.variable,
                });
              } else {
                newOutputDetails.push(output);
              }
            });
          }
          test.detail.input = newInputDetails;
          test.detail.output = newOutputDetails;
        });
        this.funcTestResultStatistic[func._id.toString()] = { passedCount };
        return result;
      }),
      errorTests,
      failedTests,
    };
    result.functions = result.functions.sort((a, b) => (a.index > b.index ? 1 : -1));
    return result;
  }

  generateFileReport(data, output) {
    const template = 'file-template';
    data.reportFooterText = this.reportFooterText;
    return (async () => {
      output = `${output}.docx`;
      const tpl = new Docx(path.join(this.baseDir, 'template', 'docx', `${template}.docx`));
      await tpl.generate(
        data,
        output,
        true
      );
      return output;
    })();
  }
}

module.exports = Reporter;
